package com.sztz.oss.minio;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.sztz.constants.CommonConstants;
import com.sztz.oss.OssConfig;
import com.sztz.result.BaseResult;
import com.sztz.result.ResultEnum;
import com.sztz.result.ServiceException;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * Minio API 模板
 * @author yzy
 * @date 2022/9/30 10:20
 */
@AllArgsConstructor
@Slf4j
public class MinioTemplate {
    /**
     * Minio客户端
     */
    MinioClient minioClient;
    /**
     * Oss配置类
     */
    OssConfig ossConfig;

    /**
     * 创建一个分桶
     * @param bucketName 桶名称
     * @author yzy
     * @date 2022/9/30 10:20
     */
    @SneakyThrows
    public void createBucket(String bucketName){
        if(!existsBucket(bucketName)){
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }
    /**
     * 给分桶设置自定义下载权限
     * @param bucketName 桶名称
     * @author yzy
     * @date 2022/9/30 10:20
     */
    @SneakyThrows
    public void setBucketCustomsPolicy(String bucketName){
        String customsPolicy="{\n" +
                "    \"Version\": \"2012-10-17\",\n" +
                "    \"Statement\": [\n" +
                "        {\n" +
                "            \"Effect\": \"Allow\",\n" +
                "            \"Principal\": {\n" +
                "                \"AWS\": [\n" +
                "                    \"*\"\n" +
                "                ]\n" +
                "            },\n" +
                "            \"Action\": [\n" +
                "                \"s3:GetBucketLocation\"\n" +
                "            ],\n" +
                "            \"Resource\": [\n" +
                "                \"arn:aws:s3:::{}\"\n" +
                "            ]\n" +
                "        },\n" +
                "        {\n" +
                "            \"Effect\": \"Allow\",\n" +
                "            \"Principal\": {\n" +
                "                \"AWS\": [\n" +
                "                    \"*\"\n" +
                "                ]\n" +
                "            },\n" +
                "            \"Action\": [\n" +
                "                \"s3:GetObject\"\n" +
                "            ],\n" +
                "            \"Resource\": [\n" +
                "                \"arn:aws:s3:::{}/*\"\n" +
                "            ]\n" +
                "        }\n" +
                "    ]\n" +
                "}";
        String policyJson = StrUtil.format(customsPolicy, bucketName, bucketName);
        if(!existsBucket(bucketName)){
            minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(policyJson).build());
        }

    }
    /**
     * 根据桶名判断分桶是否存在
     * @param bucketName 桶名称
     * @return boolean 是否存在
     * @author yzy
     * @date 2022/9/30 10:20
     */
    @SneakyThrows
    public boolean existsBucket(String bucketName){
        return  minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }
    /**
     * 获取Minio的所有桶信息
     * @return List<Bucket> 桶信息List列表
     * @author yzy
     * @date 2022/9/30 10:20
     */
    @SneakyThrows
    public List<Bucket> listBuckets(){
        return minioClient.listBuckets();
    }
    /**
     * 跟据桶名称获取Minio的桶信息,有可能不存在这个桶
     * @return Optional<Bucket> 桶信息
     * @author yzy
     * @date 2022/10/29 15:21
     */
    @SneakyThrows
    public Optional<Bucket> getBucket(String bucketName){
        return minioClient.listBuckets().stream().filter(bucket-> StrUtil.equalsAnyIgnoreCase(bucket.name(),bucketName)).findFirst();
    }
    /**
     * 根据桶名删除分桶
     * @param bucketName 桶名称
     * @author yzy
     * @date 2022/9/30 10:20
     */
    @SneakyThrows
    public void removeBucket(String bucketName){
        if(existsBucket(bucketName)) {
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
        }
    }
    /**
     * 文件上传
     * @param multipartFile 需要上传的文件
     * @param bucketName 桶名称
     * @param fileName 文件名
     * @return OssFileResponse 上传结果
     * @author yzy
     * @date 2022/11/01 17:10
     */
    public BaseResult putObject(MultipartFile multipartFile, String bucketName, String fileName){
        if(StrUtil.isBlank(bucketName)){
            bucketName = ossConfig.getDefaultBucketName();
        }
        //如果桶不存在就创建一个分桶
        createBucket(bucketName);
        //生成要上传的文件名
        String ossFileName = generateOssFileName(fileName);
        try {
            InputStream inputStream = multipartFile.getInputStream();
            PutObjectArgs putObjectArgs = PutObjectArgs.builder().bucket(bucketName).object(ossFileName).stream(inputStream, inputStream.available(), -1).build();
            ObjectWriteResponse objectWriteResponse = minioClient.putObject(putObjectArgs);
            log.info(JSONUtil.toJsonStr(objectWriteResponse));
            return BaseResult.success(ossFileName);
        }catch (Exception e){
            log.error(ExceptionUtil.stacktraceToString(e));
            return BaseResult.fail(ResultEnum.FILE_UPLOAD_FAIL,e.getMessage());
        }
    }
    /**
     * 文件下载
     * @param bucketName 桶名称
     * @param ossName 原文件名
     * @param fileName 下载文件名
     * @author yzy
     * @date 2022/11/01 19:23
     */
    @SneakyThrows
    public void downloadObject(String bucketName, String ossName, String fileName, HttpServletResponse response){
        if(!existsObject(bucketName,ossName)){
            throw new ServiceException(ResultEnum.FILE_NOT_EXISTS);
        }
        try(GetObjectResponse object = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(ossName).build());) {
            response.setHeader("Content-Disposition", StrUtil.format("attachment;filename={}" , URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())));
            response.setContentType("application/octet-stream");
            response.setCharacterEncoding("UTF-8");
            byte[] buf = new byte[1024];
            int len;
            try(OutputStream os = response.getOutputStream()){
                while((len=object.read(buf))!=-1){
                    os.write(buf,0,len);
                }
            }
        } catch (Exception e) {
            log.error(ExceptionUtil.stacktraceToString(e));
            response.setHeader("Content-type", "text/html;charset=UTF-8");
            String data = "文件下载失败";
            response.getOutputStream().write(data.getBytes(StandardCharsets.UTF_8));
        }
    }
    /**
     * 获取文件的预览URL
     * @param bucketName 桶名称
     * @param fileName 文件名
     * @param expire 过期天数，最大只能七天，默认值也是七天
     * @return OssFileResponse 上传结果
     * @author yzy
     * @date 2022/11/01 15:53
     */
    public BaseResult getObjectUrl(String bucketName, String fileName, Integer expire){
        GetPresignedObjectUrlArgs.Builder method = GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(fileName).method(Method.GET);
        if(ObjectUtil.isNotNull(expire)){
            method.expiry(expire, TimeUnit.DAYS);
        }
        GetPresignedObjectUrlArgs build = method.build();
        try {
            return BaseResult.success(minioClient.getPresignedObjectUrl(build));
        } catch (Exception e) {
            log.error(ExceptionUtil.stacktraceToString(e));
            return BaseResult.fail(ResultEnum.FILE_NOT_EXISTS,e.getMessage());
        }
    }

    public BaseResult removeObjects(String bucketName,String prefix){
        //桶可能不存在
        if(!existsBucket(bucketName)){
            return BaseResult.fail(ResultEnum.FILE_NOT_EXISTS);
        }
        List<DeleteObject> list = new ArrayList<>();
        //按前缀查出所有文件
        ListObjectsArgs build = ListObjectsArgs.builder().bucket(bucketName).recursive(true).prefix(prefix).build();
        Iterable<Result<Item>> objects = minioClient.listObjects(build);
        //批量删除文件
        RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket(bucketName).objects(list).build();
        Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
        try {
            if(!results.iterator().hasNext()){
                return BaseResult.success(StrUtil.EMPTY);
            }
            JSONArray array =  new JSONArray();
            for (Result<DeleteError> result : results) {
                DeleteError deleteError = result.get();
                JSONObject obj = new JSONObject();
                log.info("minio删除错误:{}->bucket:{},file:{}",deleteError.message(),deleteError.bucketName(),deleteError.objectName());
                obj.put("bucket",deleteError.bucketName());
                obj.put("obj",deleteError.objectName());
                obj.put("error",deleteError.message());
                array.add(obj);
            }
            return BaseResult.fail(ResultEnum.FILE_DELETE_FAIL,array);
        }catch (Exception e){
            log.error(ExceptionUtil.stacktraceToString(e));
            return BaseResult.fail(ResultEnum.FILE_DELETE_FAIL,e.getMessage());
        }
    }
    /**
     * 根据桶名跟文件名删除文件
     * @param bucketName 桶名称
     * @param fileName 文件名
     * @return BaseResult 删除结果
     * @author yzy
     * @date 2022/11/01 20:02
     */
    public BaseResult removeObject(String bucketName, String fileName){
        //文件可能不存在
        if(!existsObject(bucketName,fileName)){
            throw new ServiceException(ResultEnum.FILE_NOT_EXISTS);
        }
        RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build();
        try {
            minioClient.removeObject(removeObjectArgs);
            return BaseResult.success(StrUtil.EMPTY);
        }catch(Exception e){
            log.error(ExceptionUtil.stacktraceToString(e));
            return BaseResult.fail(ResultEnum.FILE_DELETE_FAIL,e.getMessage());
        }
    }
    /**
     * 生成上传后的文件名
     * @param fileName 原始文件名
     * @return String 新文件名
     * @author yzy
     * @date 2022/9/30 17:12
     */
    private String generateOssFileName(String fileName){
        LocalDateTime now = LocalDateTime.now();
        //每天一个文件夹
        String dateDic = DateTimeFormatter.ofPattern(DatePattern.PURE_DATE_PATTERN).format(now);
        //用时分秒作为文件前缀
        String filePrefix = DateTimeFormatter.ofPattern(DatePattern.PURE_TIME_PATTERN).format(now);
        return StrUtil.format("{}/{}_{}",dateDic, filePrefix, fileName);
    }
    /**
     * 判断需要获取的文件是否存在
     * @param bucketName 桶名
     * @param objectName 文件名
     * @return boolean true:存在 false:不存在
     * @author yzy
     * @date 2022/11/01 15:26
     */
    private boolean existsObject(String bucketName, String objectName){
        StatObjectArgs build = StatObjectArgs.builder().bucket(bucketName).object(objectName).build();
        try {
            //如果文件不存在会抛导常
            minioClient.statObject(build);
            return Boolean.TRUE;
        }catch (Exception e){
            return Boolean.FALSE;
        }
    }
}
